﻿//-----------------------------------------------------------------------
// <copyright file="ViewModelBase.cs" author="Artak Mkrtchyan">
//     Creative Commons - GNU General Public License (http://creativecommons.org/licenses/GPL/2.0/
// </copyright>
// <author>Artak Mkrtchyan</author>
// <email>mkArtak@gmail.com</email>
// <date>15/07/2012</date>
//-----------------------------------------------------------------------
using ConsoleHoster.Common.Utilities;
using ConsoleHoster.Common.Utilities.Threading;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace ConsoleHoster.Common.ViewModel
{
    public abstract class ViewModelBase : PropertyChangeNotifierBase
    {
        private ConcurrentResource<int> busyCallCounter = new ConcurrentResource<int>(0);
        private string busyStatus = null;
        private string errorMessage = null;
        private readonly bool isInDesignMode;
        private IDispatcher dispatcher;
        private readonly ILogger logger;

        public ViewModelBase(IDispatcher argDispatcher, ILogger argLogger)
        {
            this.logger = argLogger ?? EmptyLogger.Instance;
            this.dispatcher = argDispatcher;
            this.isInDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject());
        }

        /// <summary>
        /// This method is designed to be the entry point after for the app to do the real loading/sturtup phase.
        /// Each component, which need to do some stuff, need to override and implement the InitializeRuntimeMode() method and also,
        /// if required for design-time support) call the InitializeDesignMode() Method
        /// </summary>
        public void Initialize()
        {
            if (this.IsInDesignMode)
            {
                this.InitializeDesignMode();
            }
            else
            {
                this.InitializeRuntimeMode();
            }
        }

        protected virtual void InitializeDesignMode()
        {

        }

        protected virtual void InitializeRuntimeMode()
        {

        }

        protected bool RunActionWithErrorHandling(Action argAction)
        {
            return this.RunActionWithErrorHandling(argAction, ex => this.HandleError(null, ex));
        }

        protected virtual void RunActionSwitchingToBusyState(Action argAction, bool argHandleErrors, string argBusyStatus = null)
        {
            try
            {
                this.SwitchToBusyState(argBusyStatus);
                argAction();
            }
            catch (Exception ex)
            {
                if (argHandleErrors)
                {
                    this.HandleError(null, ex);
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                this.ResetBusyState();
            }
        }

        /// <summary>
        /// Runs the given asynchronouse action with an option to switch to busy state before running.
        /// </summary>
        /// <param name="argAction">The action to execute</param>
        /// <param name="argSwitchToBusyState">Indicates, whether to switch to busy state before executing action or not.</param>
        /// <param name="argBusyStatus">Specifies the busy status, to be set, while the action is executing</param>
        /// <param name="argHandleErrors">If true, handles the errors internally, otherwise - lets those to propagate.</param>
        /// <returns>The Task instance, representing the asynchronous action</returns>
        protected async Task RunAsyncAction(Action argAction, bool argSwitchToBusyState, string argBusyStatus = null, bool argHandleErrors = true)
        {
            await this.RunAsyncAction(async () => await Task.Factory.StartNew(argAction), argSwitchToBusyState, argBusyStatus, argHandleErrors);
        }

        /// <summary>
        /// Runs the given asynchronouse action with an option to switch to busy state before running.
        /// </summary>
        /// <param name="argAction">The action to execute</param>
        /// <param name="argSwitchToBusyState">Indicates, whether to switch to busy state before executing action or not.</param>
        /// <param name="argBusyStatus">Specifies the busy status, to be set, while the action is executing</param>
        /// <param name="argHandleErrors">If true, handles the errors internally, otherwise - lets those to propagate.</param>
        /// <returns>The Task instance, representing the asynchronous action</returns>
        protected async Task RunAsyncAction(Func<Task> argAction, bool argSwitchToBusyState, string argBusyStatus = null, bool argHandleErrors = true)
        {
            try
            {
                if (argSwitchToBusyState)
                {
                    this.SwitchToBusyState(argBusyStatus);
                }

                await argAction();

                if (argSwitchToBusyState)
                {
                    this.ResetBusyState();
                }
            }
            catch (Exception ex)
            {
                if (argSwitchToBusyState)
                {
                    this.ResetBusyState();
                }

                if (argHandleErrors)
                {
                    this.HandleError("Error occured during async action execution", ex);
                }
                else
                {
                    throw new ApplicationException("Error occured during async action execution", ex);
                }
            }
        }

        private async void SwitchToBusyState(string argBusyStatus)
        {
            await this.RunActionOnUIThread(() =>
            {
                this.IsBusy = true;
                this.BusyStatus = argBusyStatus;
            }, false, DispatcherPriority.Background);
        }

        private async void ResetBusyState()
        {
            await this.RunActionOnUIThread(() =>
            {
                this.IsBusy = false;
            }, false, DispatcherPriority.Background);
        }

        protected virtual bool RunActionWithErrorHandling(Action argAction, string argErrorMessage)
        {
            return this.RunActionWithErrorHandling(argAction, ex => this.HandleError(argErrorMessage, ex));
        }

        protected virtual bool RunActionWithErrorHandling(Action argAction, Action<Exception> argErrorHandler)
        {
            ValidationManager.RequireArgumentNotNull(argErrorHandler, "argErrorHandler");

            bool tmpResult = false;

            try
            {
                argAction();
                tmpResult = true;
            }
            catch (Exception ex)
            {
                argErrorHandler(ex);
            }

            return tmpResult;
        }

        protected virtual void HandleError(string argErrorMessage, Exception ex)
        {
            if (Debugger.IsAttached)
            {
                Debugger.Break();
            }

            this.Logger.LogError("{0}. Details: {1}", argErrorMessage, ex.ToString());
            this.ErrorMessage = argErrorMessage ?? ex.Message;
        }

        protected virtual void OnErrorStateChanged()
        {

        }

        protected async Task RunActionOnUIThread(Action argAction, bool argRunAsync = false, DispatcherPriority argPriority = DispatcherPriority.Normal)
        {
            ValidationManager.RequireArgumentNotNull(argAction, "argAction");

            if (!this.Dispatcher.IsInDispatcherThread())
            {
                if (argRunAsync)
                {
                    await this.Dispatcher.BeginInvoke(argAction, argPriority);
                }
                else
                {
                    this.Dispatcher.Invoke(argAction, argPriority);
                }
            }
            else
            {
                argAction();
            }
        }

        public bool IsBusy
        {
            get
            {
                return this.busyCallCounter.Resource > 0;
            }
            set
            {
                this.busyCallCounter.LockForAction(() =>
                {
                    bool tmpInitialValue = this.IsBusy;
                    int tmpNewValue = this.busyCallCounter.Resource + (value ? 1 : -1);
                    this.busyCallCounter.Resource = Math.Max(0, tmpNewValue);
                    if (this.IsBusy != tmpInitialValue)
                    {
                        this.NotifyPropertyChange("IsBusy");
                    }
                });
            }
        }

        public string BusyStatus
        {
            get
            {
                return this.busyStatus;
            }
            protected set
            {
                this.SetPropertyValue<string>("BusyStatus", ref this.busyStatus, value);
            }
        }

        public string ErrorMessage
        {
            get
            {
                return this.errorMessage;
            }
            set
            {
                if (this.SetPropertyValue<string>("ErrorMessage", ref this.errorMessage, value))
                {
                    this.OnErrorStateChanged();
                }
            }
        }

        protected bool IsInDesignMode
        {
            get
            {
                return this.isInDesignMode;
            }
        }

        protected IDispatcher Dispatcher
        {
            get
            {
                return this.dispatcher;
            }
        }

        protected ILogger Logger
        {
            get
            {
                return this.logger;
            }
        }
    }
}